define([], function () {

    var $mb = {};

    $mb.showLayer = function (map, layerId) {
        if (!(typeof layerId === 'string') && layerId.layerId) {
            layerId = layerId.layerId;
        }
        var layer = map.getLayer(layerId);
        if (!layer) return;
        map.setLayoutProperty(layerId, 'visibility', 'visible');
    };

    $mb.hideLayer = function (map, layerId) {
        if (!(typeof layerId === 'string') && layerId.layerId) {
            layerId = layerId.layerId;
        }
        var layer = map.getLayer(layerId);
        if (!layer) return;
        map.setLayoutProperty(layerId, 'visibility', 'none');
    };

    function AbstractLayer(opts, sourceOpts, layerOpts) {

        opts = opts || {};

        this.opts = opts;

        var uuidHolder = opts._uuid ? opts : this;
        uuidHolder._uuid = opts._uuid || H.fastUuid().toLowerCase();

        if (!(layerOpts instanceof Array)) {
            layerOpts = [layerOpts];
        }
        for (var i = 0; i < layerOpts.length; i++) {
            var layerId = uuidHolder._uuid;
            if (layerOpts.length > 1) {
                layerId = uuidHolder._uuid + '_' + i;
            }
            var layerOpt = layerOpts[i];
            layerOpt.id = layerId;
            layerOpt.source = uuidHolder._uuid;
        }

        this.sourceOpts = sourceOpts;
        this.layerOpts = layerOpts;

        this.currMap = null;
        this._lastMapAdded = null;

        this.setMap = function (map) {
            if (!map) {
                this.removeFromMap({map: this.currMap});
                return;
            }
            this.addToMap({map: map || this.currMap});
        };

        this.addToMap = function (scope) {
            // map without addSource might not be a valid mapbox map instance
            if (!scope || !scope.map || !scope.map.addSource) return;
            var map = scope.map;
            this.visible = true;
            if (this.currMap && map !== this.currMap) {
                this.currMap.removeSource(uuidHolder._uuid);
                for (var i = 0; i < layerOpts.length; i++) {
                    this.currMap.removeLayer(layerOpts[i].id);
                }
            }
            if (map && map !== this.currMap) {
                map.addSource(uuidHolder._uuid, this.sourceOpts);
                this.layerOpts.forEach(function (layerOpt) {
                    map.addLayer(layerOpt);
                });
            }
            this.currMap = map;
            this._lastMapAdded = map;
        };

        this.removeFromMap = function (scope) {
            var map = this.currMap;
            if (scope && scope.map) {
                map = scope.map;
            }
            if (!map) return;
            this.currMap = null;
            for (var i = 0; i < layerOpts.length; i++) {
                var layerId = layerOpts[i].id;
                var exist = map.getLayer(layerId);
                if (exist) {
                    map.removeLayer(layerId);
                }
            }
            var src = map.getSource(uuidHolder._uuid);
            if (src) {
                map.removeSource(uuidHolder._uuid);
            }
        };

        this.show = function (map) {
            map = map || this.currMap;
            if (!map) return;
            this.visible = true;
            for (var i = 0; i < layerOpts.length; i++) {
                $mb.showLayer(map, layerOpts[i].id);
            }
        };

        this.hide = function (map) {
            map = map || this.currMap;
            if (!map) return;
            this.visible = false;
            for (var i = 0; i < layerOpts.length; i++) {
                $mb.hideLayer(map, layerOpts[i].id);
            }
        };

    }

    $mb.AbstractLayer = AbstractLayer;

    return $mb;

});